package com.gitee.wsl.transform.parser.string

import com.gitee.wsl.transform.parser.ParseResult
import com.gitee.wsl.transform.parser.Parser
import com.gitee.wsl.transform.parser.ParserChar
import com.gitee.wsl.transform.parser.ParserString
import com.gitee.wsl.transform.parser.StringParser
import com.gitee.wsl.transform.parser.fail


// region Result builders
/**
 * Use for successfully parsing a value.
 * Wraps a value in a parse w/o modifying the text being parsed.
 *
 * **Haskell equivalent:**
 * - Applicative#pure()
 * - Monad#return()
 */
fun <T> Parser.Companion.pure(value: T) = StringParser{ text ->
    listOf(ParseResult(value, text))
}

fun <T : Any?, R : Any?> StringParser<T>.flatMap(
    nextParser: (T) -> StringParser<R>
): StringParser<R> = StringParser{ string ->
    val results = this.parser(string) // apply parser 1

    /*
     * Parser 1 = "this"
     * Parser 2 = "nextParser"
     * # Case A:
     * Parser 1 fails, meaning it returns res1 = []
     * => Parser 2 won't be invoked, [] (failure) is returned
     *
     * # Case B:
     * Parser 1 parses only one thing: res1 = [ParseResult<T>]
     * => Parser 2 will be invoked only once, [f(ParseResult<T>)]
     *
     * # Case C:
     * Parser 1 parses multiple things: res1 = [pr1, pr2, ... , prn]
     * => Parser 2 (`f`) will be invoked N times.
     * If Parser 2 also returns multiple results they'll be flattened and returned [n*m]
     * where n = Parser 1 results and m = Parser 2 results for each n.
     */
    results.flatMap {
        // Apply Parser 2 to each successfully parsed value by Parser 1 and its leftover
        nextParser(it.value).parser(it.leftover)
    }
}

// region Read a not parsed character
/**
 * A parser that reads one character from the text left to parse.
 * Fails if the text is empty.
 */
fun Parser.Companion.item(): ParserChar = ParserChar{ string ->
    if (string.isNotEmpty()) {
        // return the first character as value and the rest as leftover
        listOf(
            ParseResult(
                value = string.first(),
                leftover = string.drop(1)
            )
        )
    } else emptyList()
}
// endregion

// region Core: Parse char, string & a symbol satisfying a predicate
/**
 * Parses a char if it satisfies a given predicate.
 * @param predicate returns whether the parsing is successful.
 * @return a parser that parses a character for a predicate.
 */
fun Parser.Companion.sat(predicate: (Char) -> Boolean): ParserChar = ParserChar { string ->
    item().flatMap { char ->
        if (predicate(char)) pure(char) else fail()
    }.parser(string)
}

/**
 * Parses a specific character.
 * @param c the character to parse
 * @return a parser that parses a character
 */
fun Parser.Companion.char(c: Char): ParserChar = sat { it == c }

fun Parser.Companion.string(str: String): ParserString = StringParser { string ->
    if (str.isEmpty())
        pure("").parser(string)
    else {
        // recurse
        char(str.first()).flatMap{ c ->
            string(str.drop(1)).flatMap{ cs ->
                pure(c + cs)
            }
        }.parser(string)
    }
}
// endregion

/**
 * 惰性Matcher，解析时调用[matcherSupplier]获取Matcher实例并执行
 */
fun <T> Parser.Companion.lazy(matcherSupplier: () -> StringParser<T>) = StringParser { s -> matcherSupplier().parser(s) }

infix fun <T> Char.and(matcher: StringParser<T>) = Parser.char(this).plus(matcher)

infix fun <T> Char.or(matcher: StringParser<T>) = Parser.char(this).or(matcher)


